package edu.ntu.cloudroid.xacml;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;


import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.sun.xacml.EvaluationCtx;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BagAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.combine.BaseCombiningAlgFactory;
import com.sun.xacml.combine.CombiningAlgFactory;
import com.sun.xacml.combine.CombiningAlgFactoryProxy;
import com.sun.xacml.combine.StandardCombiningAlgFactory;
import com.sun.xacml.cond.EvaluationResult;
import com.sun.xacml.cond.FunctionFactory;
import com.sun.xacml.cond.FunctionFactoryProxy;
import com.sun.xacml.cond.StandardFunctionFactory;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.server.backendservice.FriendService;
import edu.ntu.cloudroid.xacml.combining.ClouDroidPolicyCombiningAlgorithm;
import edu.ntu.cloudroid.xacml.function.GroupContainsKey;
import edu.ntu.cloudroid.xacml.function.SubjectInTimeBoundsOfResource;
import edu.ntu.cloudroid.xacml.function.SubjectIsFriendOfResource;
import edu.ntu.cloudroid.xacml.function.SubjectWithinRadiusOfResource;

public class Utilities
{
   public static final Logger logger = Logger.getLogger(Utilities.class
         .getName());

   /**
    * Get value of attribute from given <code>EvaluationCtx</code>.
    * 
    * @param attrId
    *           attribute id
    * @param typeId
    *           type id
    * @param context
    *           evaluation context
    * @return the value of the attribute
    * @throws XACMLException
    *            if the attribute can not be retrieved correctly
    */
   public static AttributeValue getSubjectAttributeValue(String attrId,
         String typeId, EvaluationCtx context) throws XACMLException
   {
      try {
         EvaluationResult result = context.getSubjectAttribute(new URI(typeId),
               new URI(attrId), new URI(XACMLStrings.CATEGORY_ACCESS_SUBJECT));
         if (result == null) {
            throw new XACMLException("Could not obtain attribute with ID '"
                  + attrId + "'");
         }
         return extractFirstFromBag(result);
      } catch (URISyntaxException e) {
         throw new XACMLException("Error in parsing attribute with ID '"
               + attrId + "'", e);
      }
   }

   public static AttributeValue getEnvironmentAttributeValue(String attrId,
         String typeId, EvaluationCtx context) throws XACMLException
   {
      try {
         EvaluationResult result = context.getEnvironmentAttribute(new URI(
               typeId), new URI(attrId), null);
         if (result == null) {
            throw new XACMLException("Could not obtain attribute with ID '"
                  + attrId + "'");
         }
         return extractFirstFromBag(result);
      } catch (URISyntaxException e) {
         throw new XACMLException("Error in parsing attribute with ID '"
               + attrId + "'", e);
      }
   }

   @SuppressWarnings("unchecked")
   private static AttributeValue extractFirstFromBag(EvaluationResult result)
   {
      BagAttribute attributeBag = (BagAttribute) result.getAttributeValue();

      // Extract the first value
      Iterator<AttributeValue> it = attributeBag.iterator();
      if (it.hasNext()) {
         return it.next();
      }

      return null;
   }

   /**
    * Get value of subject's key.
    * 
    * @param context
    *           the evaluation context
    * @return value of the subject's key
    * @throws XACMLException
    *            if there is an error in retrieving value of key
    */
   public static Key getSubjectPersonKey(EvaluationCtx context)
         throws XACMLException
   {
      StringAttribute keyString = (StringAttribute) Utilities
            .getSubjectAttributeValue(XACMLStrings.SUBJECT_ID,
                  StringAttribute.identifier, context);
      if (keyString == null) {
         return null;
      }
      return KeyFactory.stringToKey(keyString.getValue());
   }

   /**
    * Get value of resource's key.
    * 
    * @param context
    *           the evaluation context
    * @return value of the resource's key
    */
   public static Key getResourcePersonKey(EvaluationCtx context)
   {
      StringAttribute resourceKeyString = (StringAttribute) context
            .getResourceId();      
      return KeyFactory.stringToKey(resourceKeyString.getValue());
   }

   @SuppressWarnings("unchecked")
   public static Key getResourcePersonKey(RequestCtx context)
         throws XACMLException
   {
      // Assume the first resource is the correct one.
      Iterator<Attribute> resources = context.getResource().iterator();      
      if (resources.hasNext()) {
         Attribute resource = resources.next();
         String resourceKeyString = ((StringAttribute) resource.getValue())
               .getValue();
         return KeyFactory.stringToKey(resourceKeyString);
      } else {
         throw new XACMLException("Could not find resource key in context.");
      }
   }
   

   public static void setUpFactory() 
   {
      // Add new functions
      FunctionFactoryProxy funcProxy = StandardFunctionFactory
            .getNewFactoryProxy();
      FunctionFactory funcFactory = funcProxy.getTargetFactory();
      funcFactory.addFunction(new GroupContainsKey());      
      funcFactory.addFunction(new SubjectInTimeBoundsOfResource());
      funcFactory.addFunction(new SubjectIsFriendOfResource());
      funcFactory.addFunction(new SubjectWithinRadiusOfResource());
      FunctionFactory.setDefaultFactory(funcProxy);

      // Add new combining algorithm
      final BaseCombiningAlgFactory algFactory = new BaseCombiningAlgFactory(
            StandardCombiningAlgFactory.getFactory().getStandardAlgorithms());
      try{
      for (IDs.PolicyCombiningAlgID id : IDs.PolicyCombiningAlgID.values()){
    	  algFactory.addAlgorithm(IDs.getPolicyCombiningAlgorithm(id.ordinal()));
      }
      }
      catch (Exception e){
    	  System.out.println("ERROR loading policy combining algorithm");
      }
      CombiningAlgFactory.setDefaultFactory(new CombiningAlgFactoryProxy() {
         @Override
         public CombiningAlgFactory getFactory()
         {
            return algFactory;
         }
      });
   }
}
